--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / backend / http / routes / messages.py
Displaying Raw • Download
meshchatx/src/backend/http/routes/messages.py master (b89dc9e6) Text, 21.21 KB
T8b949e# SPDX-License-Identifier: 0BSD
T8b949e"""HTTP routes: messages."""
Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787httpT7ee787.T7ee787meshchat_names Tff7b72import Tb4b4b4( T8b949e# noqa: F401
Te6edf3GeoValidationErrorTb4b4b4,
Te6edf3OutboundHttpBlockedErrorTb4b4b4,
Te6edf3OverlayExportErrorTb4b4b4,
Te6edf3OverlaySourceParseErrorTb4b4b4,
Te6edf3PluginSecurityErrorTb4b4b4,
Te6edf3AsyncUtilsTb4b4b4,
Te6edf3InterfaceConfigParserTb4b4b4,
Te6edf3InterfaceDiscoveryTb4b4b4,
Te6edf3InterfaceEditorTb4b4b4,
Te6edf3LOGIN_PATHTb4b4b4,
Te6edf3LXMFTb4b4b4,
Te6edf3LxmfAudioFieldTb4b4b4,
Te6edf3LxmfFileAttachmentTb4b4b4,
Te6edf3LxmfFileAttachmentsFieldTb4b4b4,
Te6edf3LxmfImageFieldTb4b4b4,
Te6edf3MAX_EXPORT_TILESTb4b4b4,
Te6edf3MarkdownRendererTb4b4b4,
Te6edf3NomadnetFileDownloaderTb4b4b4,
Te6edf3NomadnetPageDownloaderTb4b4b4,
Te6edf3RNProbeHandlerTb4b4b4,
Te6edf3RNSTb4b4b4,
Te6edf3ReticulumMeshChatTb4b4b4,
Te6edf3SETUP_PATHTb4b4b4,
Te6edf3TRANSPARENT_TILETb4b4b4,
Te6edf3TelemeterTb4b4b4,
Te6edf3UTCTb4b4b4,
Te6edf3WSMsgTypeTb4b4b4,
Te6edf3_is_chaquopy_androidTb4b4b4,
Te6edf3_is_loopback_bind_hostTb4b4b4,
Te6edf3_request_client_ipTb4b4b4,
Te6edf3aiohttpTb4b4b4,
Te6edf3app_versionTb4b4b4,
Te6edf3assert_migration_context_pathsTb4b4b4,
Te6edf3asyncioTb4b4b4,
Te6edf3base64Tb4b4b4,
Te6edf3bcryptTb4b4b4,
Te6edf3binasciiTb4b4b4,
Te6edf3build_blocklist_export_documentTb4b4b4,
Te6edf3build_export_documentTb4b4b4,
Te6edf3build_messages_export_bundleTb4b4b4,
Te6edf3cache_statsTb4b4b4,
Te6edf3cancel_inbound_deliveriesTb4b4b4,
Te6edf3castTb4b4b4,
Te6edf3compute_lxmf_conversation_unread_from_latest_rowTb4b4b4,
Te6edf3configparserTb4b4b4,
Te6edf3contextlibTb4b4b4,
Te6edf3convert_db_favourite_to_dictTb4b4b4,
Te6edf3convert_db_lxmf_message_to_dictTb4b4b4,
Te6edf3convert_lxmf_message_to_dictTb4b4b4,
Te6edf3convert_nomadnet_field_data_to_mapTb4b4b4,
Te6edf3convert_nomadnet_string_data_to_mapTb4b4b4,
Te6edf3convert_propagation_node_state_to_stringTb4b4b4,
Te6edf3copyTb4b4b4,
Te6edf3datetimeTb4b4b4,
Te6edf3describe_port_conflictTb4b4b4,
Te6edf3detect_image_format_from_magicTb4b4b4,
Te6edf3ensure_outbound_http_allowedTb4b4b4,
Te6edf3ensure_session_csrf_tokenTb4b4b4,
Te6edf3filter_announced_dicts_by_search_queryTb4b4b4,
Te6edf3fresh_storage_at_targetTb4b4b4,
Te6edf3get_cached_active_linkTb4b4b4,
Te6edf3get_file_pathTb4b4b4,
Te6edf3get_sessionTb4b4b4,
Te6edf3get_trusted_proxy_cidrsTb4b4b4,
Te6edf3gif_utilsTb4b4b4,
Te6edf3i2p_supportTb4b4b4,
Te6edf3import_messages_export_bundleTb4b4b4,
Te6edf3ioTb4b4b4,
Te6edf3is_mbtiles_filenameTb4b4b4,
Te6edf3is_path_within_dirTb4b4b4,
Te6edf3is_port_in_useTb4b4b4,
Te6edf3is_user_facing_lxmf_payloadTb4b4b4,
Te6edf3jsonTb4b4b4,
Te6edf3list_host_network_interfacesTb4b4b4,
Te6edf3list_inbound_deliveriesTb4b4b4,
Te6edf3list_portsTb4b4b4,
Te6edf3load_app_security_settingsTb4b4b4,
Te6edf3loggerTb4b4b4,
Te6edf3loggingTb4b4b4,
Te6edf3lxmf_sidebar_preview_for_conversation_latest_rowTb4b4b4,
Te6edf3memory_log_handlerTb4b4b4,
Te6edf3message_fields_have_attachmentsTb4b4b4,
Te6edf3migrate_legacy_to_targetTb4b4b4,
Te6edf3mime_for_image_typeTb4b4b4,
Te6edf3normalize_identity_storage_hashTb4b4b4,
Te6edf3normalize_lxmf_sieve_filtersTb4b4b4,
Te6edf3normalize_message_blocklistTb4b4b4,
Te6edf3osTb4b4b4,
Te6edf3parse_bool_query_paramTb4b4b4,
Te6edf3parse_import_documentTb4b4b4,
Te6edf3parse_lxmf_display_nameTb4b4b4,
Te6edf3parse_lxmf_propagation_node_app_dataTb4b4b4,
Te6edf3parse_lxmf_sieve_filters_jsonTb4b4b4,
Te6edf3parse_lxmf_stamp_costTb4b4b4,
Te6edf3parse_message_blocklist_jsonTb4b4b4,
Te6edf3parse_nomadnetwork_node_display_nameTb4b4b4,
Te6edf3platformTb4b4b4,
Te6edf3privacy_mode_enabledTb4b4b4,
Te6edf3psutilTb4b4b4,
Te6edf3purge_messages_before_cutoffTb4b4b4,
Te6edf3reTb4b4b4,
Te6edf3resolve_message_age_cutoffTb4b4b4,
Te6edf3reticulum_pathfindingTb4b4b4,
Te6edf3rotate_session_csrf_tokenTb4b4b4,
Te6edf3rrc_protocolTb4b4b4,
Te6edf3safe_path_under_dirTb4b4b4,
Te6edf3sanitize_sticker_emojiTb4b4b4,
Te6edf3sanitize_sticker_nameTb4b4b4,
Te6edf3sanitize_websocket_config_updateTb4b4b4,
Te6edf3save_app_security_settingsTb4b4b4,
Te6edf3secretsTb4b4b4,
Te6edf3shutilTb4b4b4,
Te6edf3sqlite3Tb4b4b4,
Te6edf3sticker_pack_utilsTb4b4b4,
Te6edf3sysTb4b4b4,
Te6edf3tempfileTb4b4b4,
Te6edf3threadingTb4b4b4,
Te6edf3timeTb4b4b4,
Te6edf3tracebackTb4b4b4,
Te6edf3user_agent_hashTb4b4b4,
Te6edf3validate_export_documentTb4b4b4,
Te6edf3webTb4b4b4,
Te6edf3websocket_type_requires_authTb4b4b4,
Te6edf3zipfileTb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffregister_messages_routesTb4b4b4(Te6edf3routesTb4b4b4, Te6edf3appTb4b4b4)Tb4b4b4:
T8b949e# get custom destination display name
Tf0883e@routesTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/destination/Tffd700{destination_hash}Ta5d6ff/custom-display-nameTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffdestination_custom_display_name_getTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
T8b949e# get path params
Te6edf3destination_hash Tff7b72= Te6edf3requestTff7b72.Td2a8ffmatch_infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffcustom_display_nameTa5d6ff"Tb4b4b4: Te6edf3appTff7b72.Td2a8ffget_custom_destination_display_nameTb4b4b4(
Te6edf3destination_hashTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# set custom destination display name
T8b949e# set custom destination display name
Tf0883e@routesTff7b72.Td2a8ffpostTb4b4b4(
Ta5d6ff"Ta5d6ff/api/v1/destination/Tffd700{destination_hash}Ta5d6ff/custom-display-name/updateTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffdestination_custom_display_name_updateTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
T8b949e# get path params
Te6edf3destination_hash Tff7b72= Te6edf3requestTff7b72.Td2a8ffmatch_infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
T8b949e# get request data
Te6edf3data Tff7b72= Tff7b72await Te6edf3requestTff7b72.Td2a8ffjsonTb4b4b4(Tb4b4b4)
Te6edf3display_name Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdisplay_nameTa5d6ff"Tb4b4b4)
T8b949e# update display name if provided
Tff7b72if Tffa657lenTb4b4b4(Te6edf3display_nameTb4b4b4) Tff7b72> T79c0ff0Tb4b4b4:
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffupsert_custom_display_nameTb4b4b4(
Te6edf3destination_hashTb4b4b4,
Te6edf3display_nameTb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffCustom display name has been updatedTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# otherwise remove display name
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffdelete_custom_display_nameTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffCustom display name has been removedTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# get lxmf stamp cost for the provided lxmf.delivery destination hash
T8b949e# get lxmf stamp cost for the provided lxmf.delivery destination hash
Tf0883e@routesTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/destination/Tffd700{destination_hash}Ta5d6ff/lxmf-stamp-infoTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffdestination_lxmf_stamp_infoTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
T8b949e# get path params
Te6edf3destination_hash Tff7b72= Te6edf3requestTff7b72.Td2a8ffmatch_infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
T8b949e# convert destination hash to bytes
Te6edf3destination_hash_bytes Tff7b72= Tffa657bytesTff7b72.Td2a8fffromhexTb4b4b4(Te6edf3destination_hashTb4b4b4)
T8b949e# get lxmf stamp cost from announce in database
Te6edf3lxmf_stamp_cost Tff7b72= Tff7b72None
Te6edf3announce Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffget_announce_by_hashTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72if Te6edf3announce Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3lxmf_stamp_cost Tff7b72= Te6edf3parse_lxmf_stamp_costTb4b4b4(
Te6edf3announceTb4b4b4[Ta5d6ff"Ta5d6ffapp_dataTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)
T8b949e# get outbound ticket expiry for this lxmf destination
Te6edf3lxmf_outbound_ticket_expiry Tff7b72= Te6edf3appTff7b72.Td2a8ffmessage_routerTff7b72.Td2a8ffget_outbound_ticket_expiryTb4b4b4(
Te6edf3destination_hash_bytes
Tb4b4b4)
Tff7b72if Te6edf3lxmf_outbound_ticket_expiry Tff7b72is Tff7b72not Tff7b72None Tff7b72and Tff7b72not Tffa657isinstanceTb4b4b4(
Te6edf3lxmf_outbound_ticket_expiryTb4b4b4,
Tb4b4b4(Tffa657intTb4b4b4, Tffa657floatTb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3lxmf_outbound_ticket_expiry Tff7b72= Tff7b72None
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6fflxmf_stamp_infoTa5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ffstamp_costTa5d6ff"Tb4b4b4: Te6edf3lxmf_stamp_costTb4b4b4,
Ta5d6ff"Ta5d6ffoutbound_ticket_expiryTa5d6ff"Tb4b4b4: Te6edf3lxmf_outbound_ticket_expiryTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# get interface stats
T8b949e# mark notifications as viewed
Tf0883e@routesTff7b72.Td2a8ffpostTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/notifications/mark-as-viewedTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffnotifications_mark_as_viewedTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
Te6edf3data Tff7b72= Tff7b72await Te6edf3requestTff7b72.Td2a8ffjsonTb4b4b4(Tb4b4b4)
Te6edf3destination_hashes Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashesTa5d6ff"Tb4b4b4, Tb4b4b4[Tb4b4b4]Tb4b4b4)
Te6edf3notification_ids Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffnotification_idsTa5d6ff"Tb4b4b4, Tb4b4b4[Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3destination_hashesTb4b4b4:
T8b949e# mark LXMF conversations as viewed
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffmark_all_notifications_as_viewedTb4b4b4(
Te6edf3destination_hashesTb4b4b4,
Tb4b4b4)
T8b949e# Keep conversation read state in sync
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffmark_conversations_as_readTb4b4b4(Te6edf3destination_hashesTb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# mark all LXMF conversations as viewed if no hashes provided
T8b949e# (this happens when "Clear All" is clicked)
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffmark_all_notifications_as_viewedTb4b4b4(Tb4b4b4)
T8b949e# Also mark all conversations as read
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffmark_all_conversations_as_readTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3notification_idsTb4b4b4:
T8b949e# mark system notifications as viewed
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmiscTff7b72.Td2a8ffmark_notifications_as_viewedTb4b4b4(Te6edf3notification_idsTb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# mark all system notifications as viewed if no ids provided
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmiscTff7b72.Td2a8ffmark_notifications_as_viewedTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffokTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tf0883e@routesTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/notificationsTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffnotifications_getTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
Te6edf3not_ready Tff7b72= Te6edf3appTff7b72.Td2a8ff_require_identity_context_readyTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3not_ready Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3not_ready
Tff7b72tryTb4b4b4:
Te6edf3filter_unread Tff7b72= Te6edf3parse_bool_query_paramTb4b4b4(
Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffunreadTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fffalseTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3limit Tff7b72= Tffa657intTb4b4b4(Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fflimitTa5d6ff"Tb4b4b4, T79c0ff50Tb4b4b4)Tb4b4b4)
T8b949e# 1. Fetch system notifications
Te6edf3system_notifications Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmiscTff7b72.Td2a8ffget_notificationsTb4b4b4(
Te6edf3filter_unreadTff7b72=Te6edf3filter_unreadTb4b4b4,
Te6edf3limitTff7b72=Te6edf3limitTb4b4b4,
Tb4b4b4)
T8b949e# 2. Fetch unread LXMF conversations if requested
Te6edf3conversations Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3user_facing_peer_hashes Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)
Te6edf3total_unread_peer_hashes Tff7b72= Tffa657setTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3filter_unreadTb4b4b4:
Te6edf3local_hash Tff7b72= Te6edf3appTff7b72.Td2a8fflocal_lxmf_destinationTff7b72.Td2a8ffhexhash
Te6edf3db_conversations Tff7b72= Te6edf3appTff7b72.Td2a8ffmessage_handlerTff7b72.Td2a8ffget_conversationsTb4b4b4(
Te6edf3local_hashTb4b4b4,
Te6edf3filter_unreadTff7b72=Tff7b72TrueTb4b4b4,
Tb4b4b4)
Tff7b72for Te6edf3db_message Tff7b72in Te6edf3db_conversationsTb4b4b4:
T8b949e# Convert to dict if needed
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3db_messageTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3db_message Tff7b72= Tffa657dictTb4b4b4(Te6edf3db_messageTb4b4b4)
T8b949e# determine other user hash
Tff7b72if Te6edf3db_messageTb4b4b4[Ta5d6ff"Ta5d6ffsource_hashTa5d6ff"Tb4b4b4] Tff7b72== Te6edf3local_hashTb4b4b4:
Te6edf3other_user_hash Tff7b72= Te6edf3db_messageTb4b4b4[Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4]
Tff7b72elseTb4b4b4:
Te6edf3other_user_hash Tff7b72= Te6edf3db_messageTb4b4b4[Ta5d6ff"Ta5d6ffsource_hashTa5d6ff"Tb4b4b4]
Tff7b72if Tff7b72not Te6edf3appTff7b72.Td2a8ff_lxmf_sieve_suppresses_notificationsTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Te6edf3message_titleTff7b72=Te6edf3db_messageTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3message_contentTff7b72=Te6edf3db_messageTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffis_notification_viewedTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Te6edf3db_messageTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3total_unread_peer_hashesTff7b72.Td2a8ffaddTb4b4b4(Te6edf3other_user_hashTb4b4b4)
Te6edf3latest_for_preview Tff7b72= Te6edf3db_message
Tff7b72if Tff7b72not Te6edf3is_user_facing_lxmf_payloadTb4b4b4(
Te6edf3db_messageTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3db_messageTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3db_messageTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3latest_user_facing Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffget_latest_user_facing_incoming_messageTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3latest_user_facing Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72continue
T8b949e# Compare against last_read_at on the original row
Te6edf3last_read_at_raw Tff7b72= Te6edf3db_messageTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3last_read_at_rawTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3last_read_dt Tff7b72= Te6edf3datetimeTff7b72.Td2a8fffromisoformatTb4b4b4(
Te6edf3last_read_at_rawTb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3last_read_dtTff7b72.Td2a8fftzinfo Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3last_read_dt Tff7b72= Te6edf3last_read_dtTff7b72.Td2a8ffreplaceTb4b4b4(
Te6edf3tzinfoTff7b72=Te6edf3UTCTb4b4b4,
Tb4b4b4)
Tff7b72if Tb4b4b4(
Te6edf3latest_user_facingTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4]
Tff7b72<Tff7b72= Te6edf3last_read_dtTff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)
Tb4b4b4)Tb4b4b4:
Tff7b72continue
Tff7b72except Tb4b4b4(Tf85149ValueErrorTb4b4b4, Tf85149TypeErrorTb4b4b4)Tb4b4b4:
Tff7b72pass
Te6edf3latest_for_preview Tff7b72= Te6edf3latest_user_facing
Tff7b72if Te6edf3appTff7b72.Td2a8ff_lxmf_sieve_suppresses_notificationsTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Te6edf3message_titleTff7b72=Te6edf3latest_for_previewTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3message_contentTff7b72=Te6edf3latest_for_previewTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72continue
T8b949e# Check if notification has been viewed
Tff7b72if Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffis_notification_viewedTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Te6edf3latest_for_previewTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72continue
Te6edf3user_facing_peer_hashesTff7b72.Td2a8ffaddTb4b4b4(Te6edf3other_user_hashTb4b4b4)
T8b949e# Determine display name
Te6edf3display_name Tff7b72= Te6edf3appTff7b72.Td2a8ffget_lxmf_conversation_nameTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Tb4b4b4)
Te6edf3custom_display_name Tff7b72= Tb4b4b4(
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffget_custom_display_nameTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Tb4b4b4)
Tb4b4b4)
T8b949e# Determine latest message data
Te6edf3latest_message_data Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4: Te6edf3latest_for_previewTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4: Te6edf3latest_for_previewTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4, T79c0ff0Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffis_incomingTa5d6ff"Tb4b4b4: Te6edf3latest_for_previewTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffis_incomingTa5d6ff"Tb4b4b4) Tff7b72== T79c0ff1Tb4b4b4,
Tb4b4b4}
Te6edf3icon Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmiscTff7b72.Td2a8ffget_user_iconTb4b4b4(Te6edf3other_user_hashTb4b4b4)
Te6edf3peer_preview_name Tff7b72= Tb4b4b4(
Te6edf3custom_display_name Tff7b72or Te6edf3display_name Tff7b72or Ta5d6ff"Ta5d6ffAnonymous PeerTa5d6ff"
Tb4b4b4)
Te6edf3conversationsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6fftypeTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6fflxmf_messageTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4: Te6edf3other_user_hashTb4b4b4,
Ta5d6ff"Ta5d6ffdisplay_nameTa5d6ff"Tb4b4b4: Te6edf3display_nameTb4b4b4,
Ta5d6ff"Ta5d6ffcustom_display_nameTa5d6ff"Tb4b4b4: Te6edf3custom_display_nameTb4b4b4,
Ta5d6ff"Ta5d6fflxmf_user_iconTa5d6ff"Tb4b4b4: Tffa657dictTb4b4b4(Te6edf3iconTb4b4b4) Tff7b72if Te6edf3icon Tff7b72else Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6fflatest_message_previewTa5d6ff"Tb4b4b4: Tb4b4b4(
Te6edf3lxmf_sidebar_preview_for_conversation_latest_rowTb4b4b4(
Tffa657dictTb4b4b4(Te6edf3latest_for_previewTb4b4b4)Tb4b4b4,
Te6edf3local_hashTff7b72=Te6edf3local_hashTb4b4b4,
Te6edf3peer_display_nameTff7b72=Te6edf3peer_preview_nameTb4b4b4,
Tb4b4b4)Tb4b4b4[Tb4b4b4:T79c0ff100Tb4b4b4]
Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffupdated_atTa5d6ff"Tb4b4b4: Te6edf3datetimeTff7b72.Td2a8fffromtimestampTb4b4b4(
Te6edf3latest_message_dataTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4] Tff7b72or T79c0ff0Tb4b4b4,
Te6edf3UTCTb4b4b4,
Tb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# Combine and sort by timestamp
Te6edf3all_notifications Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3n Tff7b72in Te6edf3system_notificationsTb4b4b4:
T8b949e# Convert to dict if needed
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3nTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3n Tff7b72= Tffa657dictTb4b4b4(Te6edf3nTb4b4b4)
T8b949e# Get remote user info if possible
Te6edf3display_name Tff7b72= Ta5d6ff"Ta5d6ffUnknownTa5d6ff"
Te6edf3icon Tff7b72= Tff7b72None
Tff7b72if Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6fftypeTa5d6ff"Tb4b4b4] Tff7b72== Ta5d6ff"Ta5d6ffrrc_mentionTa5d6ff"Tb4b4b4:
Te6edf3display_name Tff7b72= Te6edf3nTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4) Tff7b72or Ta5d6ff"Ta5d6ffRelay ChatTa5d6ff"
Tff7b72elif Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6ffremote_hashTa5d6ff"Tb4b4b4]Tb4b4b4:
T8b949e# Try to find associated LXMF hash for telephony identity hash
Te6edf3lxmf_hash Tff7b72= Te6edf3appTff7b72.Td2a8ffget_lxmf_destination_hash_for_identity_hashTb4b4b4(
Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6ffremote_hashTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3lxmf_hashTb4b4b4:
T8b949e# Fallback to direct name lookup by identity hash
Te6edf3display_name Tff7b72= Tb4b4b4(
Te6edf3appTff7b72.Td2a8ffget_name_for_identity_hashTb4b4b4(Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6ffremote_hashTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72or Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6ffremote_hashTa5d6ff"Tb4b4b4]
Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3display_name Tff7b72= Te6edf3appTff7b72.Td2a8ffget_lxmf_conversation_nameTb4b4b4(
Te6edf3lxmf_hashTb4b4b4,
Tb4b4b4)
Te6edf3icon Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmiscTff7b72.Td2a8ffget_user_iconTb4b4b4(Te6edf3lxmf_hashTb4b4b4)
Te6edf3all_notificationsTff7b72.Td2a8ffappendTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffidTa5d6ff"Tb4b4b4: Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6ffidTa5d6ff"Tb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6fftypeTa5d6ff"Tb4b4b4: Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6fftypeTa5d6ff"Tb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4: Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6ffremote_hashTa5d6ff"Tb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6ffdisplay_nameTa5d6ff"Tb4b4b4: Te6edf3display_nameTb4b4b4,
Ta5d6ff"Ta5d6fflxmf_user_iconTa5d6ff"Tb4b4b4: Tffa657dictTb4b4b4(Te6edf3iconTb4b4b4) Tff7b72if Te6edf3icon Tff7b72else Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4: Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4: Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6ffis_viewedTa5d6ff"Tb4b4b4: Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6ffis_viewedTa5d6ff"Tb4b4b4] Tff7b72== T79c0ff1Tb4b4b4,
Ta5d6ff"Ta5d6ffupdated_atTa5d6ff"Tb4b4b4: Te6edf3datetimeTff7b72.Td2a8fffromtimestampTb4b4b4(
Te6edf3nTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4] Tff7b72or T79c0ff0Tb4b4b4,
Te6edf3UTCTb4b4b4,
Tb4b4b4)Tff7b72.Td2a8ffisoformatTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Te6edf3all_notificationsTff7b72.Td2a8ffextendTb4b4b4(Te6edf3conversationsTb4b4b4)
T8b949e# Sort by updated_at descending
Te6edf3all_notificationsTff7b72.Td2a8ffsortTb4b4b4(Te6edf3keyTff7b72=Tff7b72lambda Te6edf3xTb4b4b4: Te6edf3xTb4b4b4[Ta5d6ff"Ta5d6ffupdated_atTa5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3reverseTff7b72=Tff7b72TrueTb4b4b4)
T8b949e# Calculate actual unread count
Te6edf3unread_count Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmiscTff7b72.Td2a8ffget_unread_notification_countTb4b4b4(Tb4b4b4)
T8b949e# Add LXMF unread count using the same user-facing filter as
T8b949e# the listing above so the badge can never disagree with the
T8b949e# dropdown contents (no false bell triggers from reactions,
T8b949e# telemetry, icon updates, or empty payloads).
Te6edf3lxmf_unread_count Tff7b72= T79c0ff0
Te6edf3lxmf_total_unread_count Tff7b72= T79c0ff0
Te6edf3local_hash Tff7b72= Te6edf3appTff7b72.Td2a8fflocal_lxmf_destinationTff7b72.Td2a8ffhexhash
Tff7b72if Te6edf3filter_unreadTb4b4b4:
T8b949e# Already computed during the listing pass.
Te6edf3lxmf_unread_count Tff7b72= Tffa657lenTb4b4b4(Te6edf3user_facing_peer_hashesTb4b4b4)
Te6edf3lxmf_total_unread_count Tff7b72= Tffa657lenTb4b4b4(Te6edf3total_unread_peer_hashesTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3unread_conversations Tff7b72= Te6edf3appTff7b72.Td2a8ffmessage_handlerTff7b72.Td2a8ffget_conversationsTb4b4b4(
Te6edf3local_hashTb4b4b4,
Te6edf3filter_unreadTff7b72=Tff7b72TrueTb4b4b4,
Tb4b4b4)
Tff7b72for Te6edf3conv Tff7b72in Te6edf3unread_conversations Tff7b72or Tb4b4b4[Tb4b4b4]Tb4b4b4:
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3convTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3conv Tff7b72= Tffa657dictTb4b4b4(Te6edf3convTb4b4b4)
Tff7b72if Te6edf3convTb4b4b4[Ta5d6ff"Ta5d6ffsource_hashTa5d6ff"Tb4b4b4] Tff7b72== Te6edf3local_hashTb4b4b4:
Te6edf3other_user_hash Tff7b72= Te6edf3convTb4b4b4[Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4]
Tff7b72elseTb4b4b4:
Te6edf3other_user_hash Tff7b72= Te6edf3convTb4b4b4[Ta5d6ff"Ta5d6ffsource_hashTa5d6ff"Tb4b4b4]
T8b949e# Total unread count (regardless of user-facing)
Tff7b72if Tff7b72not Te6edf3appTff7b72.Td2a8ff_lxmf_sieve_suppresses_notificationsTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Te6edf3message_titleTff7b72=Te6edf3convTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3message_contentTff7b72=Te6edf3convTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72if Tff7b72not Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffis_notification_viewedTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Te6edf3convTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3lxmf_total_unread_count Tff7b72+Tff7b72= T79c0ff1
Te6edf3latest_for_check Tff7b72= Te6edf3conv
Tff7b72if Tff7b72not Te6edf3is_user_facing_lxmf_payloadTb4b4b4(
Te6edf3convTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffieldsTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3convTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3convTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3latest_user_facing Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffget_latest_user_facing_incoming_messageTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3latest_user_facing Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72continue
Te6edf3last_read_at_raw Tff7b72= Te6edf3convTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fflast_read_atTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3last_read_at_rawTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3last_read_dt Tff7b72= Te6edf3datetimeTff7b72.Td2a8fffromisoformatTb4b4b4(
Te6edf3last_read_at_rawTb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3last_read_dtTff7b72.Td2a8fftzinfo Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3last_read_dt Tff7b72= Te6edf3last_read_dtTff7b72.Td2a8ffreplaceTb4b4b4(
Te6edf3tzinfoTff7b72=Te6edf3UTCTb4b4b4,
Tb4b4b4)
Tff7b72if Tb4b4b4(
Te6edf3latest_user_facingTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4]
Tff7b72<Tff7b72= Te6edf3last_read_dtTff7b72.Td2a8fftimestampTb4b4b4(Tb4b4b4)
Tb4b4b4)Tb4b4b4:
Tff7b72continue
Tff7b72except Tb4b4b4(Tf85149ValueErrorTb4b4b4, Tf85149TypeErrorTb4b4b4)Tb4b4b4:
Tff7b72pass
Te6edf3latest_for_check Tff7b72= Te6edf3latest_user_facing
Tff7b72if Te6edf3appTff7b72.Td2a8ff_lxmf_sieve_suppresses_notificationsTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Te6edf3message_titleTff7b72=Te6edf3latest_for_checkTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftitleTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3message_contentTff7b72=Te6edf3latest_for_checkTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffcontentTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72continue
Tff7b72if Tff7b72not Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffis_notification_viewedTb4b4b4(
Te6edf3other_user_hashTb4b4b4,
Te6edf3latest_for_checkTb4b4b4[Ta5d6ff"Ta5d6fftimestampTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3lxmf_unread_count Tff7b72+Tff7b72= T79c0ff1
Te6edf3total_unread_count Tff7b72= Te6edf3unread_count Tff7b72+ Te6edf3lxmf_unread_count
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffnotificationsTa5d6ff"Tb4b4b4: Te6edf3all_notificationsTb4b4b4[Tb4b4b4:Te6edf3limitTb4b4b4]Tb4b4b4,
Ta5d6ff"Ta5d6ffunread_countTa5d6ff"Tb4b4b4: Te6edf3total_unread_countTb4b4b4,
Ta5d6ff"Ta5d6fflxmf_total_unread_countTa5d6ff"Tb4b4b4: Te6edf3lxmf_total_unread_countTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Te6edf3RNSTff7b72.Td2a8fflogTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffError in notifications_get: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4, Te6edf3RNSTff7b72.Td2a8ffLOG_ERRORTb4b4b4)
Te6edf3detail Tff7b72= Tffa657strTb4b4b4(Te6edf3eTb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Te6edf3status Tff7b72= Tb4b4b4(
T79c0ff503
Tff7b72if Tb4b4b4(
Tffa657isinstanceTb4b4b4(Te6edf3eTb4b4b4, Te6edf3sqlite3Tff7b72.Td2a8ffOperationalErrorTb4b4b4)
Tff7b72or Ta5d6ff"Ta5d6ffunable to open database fileTa5d6ff" Tff7b72in Te6edf3detail
Tff7b72or Ta5d6ff"Ta5d6ffdatabase is lockedTa5d6ff" Tff7b72in Te6edf3detail
Tb4b4b4)
Tff7b72else T79c0ff500
Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6fferrorTa5d6ff"Tb4b4b4: Tb4b4b4(
Ta5d6ff"Ta5d6ffDatabase temporarily unavailable. Retry shortly.Ta5d6ff"
Tff7b72if Te6edf3status Tff7b72== T79c0ff503
Tff7b72else Ta5d6ff"Ta5d6ffInternal errorTa5d6ff"
Tb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=Te6edf3statusTb4b4b4,
Tb4b4b4)
T8b949e# get blocked destinations
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────